package com.example.schooljava.exception;

import com.alibaba.fastjson.JSONObject;
import com.example.schooljava.annotation.RequestLimit;
import com.example.schooljava.util.HttpStatus;
import com.example.schooljava.util.Result;
import com.example.schooljava.util.TokenUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 功能描述: 拦截的具体操作，权限的认证拦截
 * @Author: LDH
 * @Date: 2022/6/17 16:37
 */
@Slf4j
@Component
public class AuthHandlerInterceptor implements HandlerInterceptor {
    @Autowired
    TokenUtil tokenUtil;
    @org.springframework.beans.factory.annotation.Value("${token.privateKey}")
    private String privateKey;
    @org.springframework.beans.factory.annotation.Value("${token.yangToken}")
    private Long yangToken;
    @Value("${token.oldToken}")
    private Long oldToken;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 权限认证的拦截操作.
     */
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        log.info("=======进入拦截器========");
        try {

            /**
             * isAssignableFrom() 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同，或是否是其超类或超接口
             * isAssignableFrom()方法是判断是否为某个类的父类
             * instanceof关键字是判断是否某个类的子类
             */
            if(object.getClass().isAssignableFrom(HandlerMethod.class)){
                //HandlerMethod 封装方法定义相关的信息,如类,方法,参数等
                HandlerMethod handlerMethod = (HandlerMethod) object;
                Method method = handlerMethod.getMethod();
                // 获取方法中是否包含注解
                RequestLimit methodAnnotation = method.getAnnotation(RequestLimit.class);
                //获取 类中是否包含注解，也就是controller 是否有注解
                RequestLimit classAnnotation = method.getDeclaringClass().getAnnotation(RequestLimit.class);
                // 如果 方法上有注解就优先选择方法上的参数，否则类上的参数
                RequestLimit requestLimit = methodAnnotation != null?methodAnnotation:classAnnotation;
                if(requestLimit != null){
                    if(isLimit(httpServletRequest,requestLimit)){
                        responseOut(httpServletResponse, new Result<>().error("","请求频繁,请稍后再试",HttpStatus.CONFLICT));
                        return false;
                    }
                }
            }

            // 如果不是映射到方法直接通过,可以访问资源.
            if (!(object instanceof HandlerMethod)) {
                return true;
            }
            //为空就返回错误
            String token = httpServletRequest.getHeader("token");
            if (null == token || "".equals(token.trim())) {
                responseOut(httpServletResponse,new Result<>().error("","身份令牌为空",HttpStatus.UNAUTHORIZED));
                return false;
            }
            else {
                log.info("token:" + token);
                Map<String, String> map = tokenUtil.parseToken(token);
                String userId = map.get("userId");
                String userRole = map.get("userRole");
                long timeOfUse = System.currentTimeMillis() - Long.parseLong(map.get("timeStamp"));
                //1.判断 token 是否过期
                //年轻 token
                if (timeOfUse < yangToken) {
                    log.info("token 未过期");
                }
                //老年 token 就刷新 token
                else if (timeOfUse >= yangToken && timeOfUse < oldToken) {
                    httpServletResponse.setHeader("token",tokenUtil.getToken(userId,userRole));
                }
                //过期 token 就返回 token 无效.
                else {
                    new Thread(() -> {
                        //删除redis中对应的key
                        redisTemplate.delete(userId);
                    }).start();

                    responseOut(httpServletResponse,new Result<>().error("","授权过期",HttpStatus.FORBIDDEN));
                    return false;
                }
                //2.角色匹配.

                //通过handler
                HandlerMethod h = (HandlerMethod) object;
                //获取接口上的reqeustmapping注解
                RequestMapping annotation = h.getMethodAnnotation(RequestMapping.class);
                //获取当前请求接口中的name属性
                assert annotation != null;
                String name = annotation.name();
                if (name.equals(""))return true;
                String[] names = name.split(",");

                // 遍历用户的身份数组
                String s=userRole.substring(1,userRole.length()-1);
                String[] st = s.split(", ");
                for (String ss : st) {
                    for (String ns : names) {
                        // 检查权限是否匹配
                        if (ns.equals(ss)) {
                            return true;
                        }
                    }
                }
                responseOut(httpServletResponse,new Result<>().error("","访问权限不匹配", HttpStatus.UNAUTHORIZED));
                return false;
            }
        }catch (Exception e){
            responseOut(httpServletResponse,new Result<>().error("","身份校验失败",HttpStatus.UNAUTHORIZED));
            log.info("toke校验异常: {}", e.getMessage());
        }finally {
            log.info("=======退出拦截器========");
        }

        return false;
    }

    //判断请求是否受限
    public boolean isLimit(HttpServletRequest request,RequestLimit requestLimit){
        // 受限的redis 缓存key ,因为这里用浏览器做测试，我就用sessionid 来做唯一key,如果是app ,可以使用 用户ID 之类的唯一标识。
        String limitKey = request.getServletPath()+request.getSession().getId();
        // 从缓存中获取，当前这个请求访问了几次
        Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey);
        if(redisCount == null){
            //初始 次数
            redisTemplate.opsForValue().set(limitKey,1,requestLimit.second(), TimeUnit.SECONDS);
        }else{
            if(redisCount.intValue() >= requestLimit.maxCount()){
                return true;
            }
            // 次数自增
            redisTemplate.opsForValue().increment(limitKey);
        }
        return false;
    }

    /**
     * 回写给客户端
     * @param response
     * @param result
     * @throws IOException
     */
    private void responseOut(HttpServletResponse response, Result result) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter out = null ;
        String json = JSONObject.toJSON(result).toString();
        out = response.getWriter();
        out.append(json);
    }
}
